/*
    hisportdetect.c - a user-space program to scan for HiSport devices
    Copyright (C) 2024  HiSport Tools

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
    MA 02110-1301 USA.
*/

#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <fcntl.h>
#include "hisportdriver.h"
#include "version.h"

/* 定义MDBCTL命令路径，与i2cbusses.c保持一致 */
#define MDBCTL "/opt/bmc/skynet/lua /opt/bmc/apps/mdbctl/service/mdbctl.lua"

#define MODE_AUTO	0
#define MODE_QUICK	1
#define MODE_READ	2
#define MODE_FUNC	3

static void help(void)
{
	fprintf(stderr,
		"Usage: hisportdetect [-a] HISPORTBUS [FIRST LAST]\n"
		"       hisportdetect -l\n"
		"  HISPORTBUS is an integer or a HiSport bus name\n"
		"  If provided, FIRST and LAST limit the probing range.\n");
}

/* 准备HiSport传输参数的函数 */
static void prepare_hisport_params(uint32_t bus_id, uint8_t device_addr, uint32_t offset, uint8_t offset_width, 
                                  struct hisport_data_s *read_params, uint8_t *rx_data) 
{
    memset(read_params, 0, sizeof(struct hisport_data_s));
    read_params->bus_id = bus_id;
    read_params->addr = (device_addr << 1) & 0xFE;;
    read_params->offset = offset;
    read_params->offset_width = offset_width;
    read_params->data_width = 1;  // 默认数据宽度为1字节
    read_params->rw_flag = 1;     // 读操作
    read_params->read_cnt = 1;    // 读取1次
    read_params->write_cnt = 10;

    read_params->rx_data_len = 1;
    read_params->rx_data = rx_data;
}

/* 列出所有可用的HiSport总线 */
static int print_hisport_busses(void)
{
	FILE *fp;
	char cmd[1024];  /* Increased buffer size to avoid truncation */
    char buffer[1024];
	int found = 0;

	snprintf(cmd, sizeof(cmd), "%s lsobj Hisport", MDBCTL);
	fp = popen(cmd, "r");
	if (fp == NULL) {
		perror("popen failed");
		return 1;
	}

	/* 逐行读取命令输出 */
	while (fgets(buffer, sizeof(buffer), fp) != NULL) {
		char num_str[3]; // 存储数字部分的字符串
		char remaining[1024]; // 存储剩余部分
		if (sscanf(buffer, "Hisport_%2[0-9]%s", num_str, remaining) == 2) {
			continue;
		} else if (sscanf(buffer, "Hisport_%2[0-9]", num_str) == 1) {
			printf("hisport-%-3s \n", num_str);
			found = 1;
		}
	}

	/* 关闭管道 */
	pclose(fp);

	/* 如果没有找到任何Hisport总线，显示提示信息 */
	if (!found) {
		printf("No HiSport buses found via mdbctl.\n");
	}

	return 0;
}

/* 查找HiSport总线号 */
static int lookup_hisport_bus(const char *hisportbus_arg)
{
	unsigned long hisportbus;
	char *end;

	hisportbus = strtoul(hisportbus_arg, &end, 0);
	if (*end || !*hisportbus_arg) {
		/* 不是数字，可能是名称？ */
		if (strncmp(hisportbus_arg, "hisport-", 8) == 0) {
			hisportbus = strtoul(hisportbus_arg + 8, &end, 0);
			if (*end) {
				fprintf(stderr, "Error: HiSport bus name doesn't match any "
					"bus present!\n");
				return -1;
			}
		} else {
			fprintf(stderr, "Error: HiSport bus name doesn't match any "
				"bus present!\n");
			return -1;
		}
	}
	if (hisportbus > 0xFFFFF) {
		fprintf(stderr, "Error: HiSport bus out of range!\n");
		return -2;
	}

	return hisportbus;
}

static int scan_hisport_bus(int hisportbus, int mode, int first, int last)
{
	int i, j;
	int res;
	int fd = -1;

	/* Open HiSport device */
	fd = hisport_open();
	if (fd < 0) {
		fprintf(stderr, "Error: Could not open HiSport device\n");
		return -1;
	}

	/* Initialize HiSport bus */
	if (hisport_init(fd) != 0) {
		fprintf(stderr, "Error: Could not initialize HiSport bus %d\n", hisportbus);
		hisport_close(fd);
		return -1;
	}

	printf("     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f\n");

	for (i = 0; i < 128; i += 16) {
		printf("%02x: ", i);
		for(j = 0; j < 16; j++) {
			fflush(stdout);

			/* Note: mode is currently ignored as we only use read operation */
			(void)mode;

			/* Skip unwanted addresses */
			if (i+j < first || i+j > last) {
				printf("   ");
				continue;
			}

			/* Probe this address using hisportdriver interface */
			uint32_t offset = 0;       /* 偏移值，对于检测通常为0 */
			uint8_t rx_data[1];        /* 传输数据缓冲区 */
			struct hisport_data_s read_params;
			bool is_found = false;
			
			/* 尝试不同的偏移量宽度 */
			for (uint8_t offset_width = 0; offset_width <= 4; offset_width++)
			{
				prepare_hisport_params((uint32_t)hisportbus, (uint8_t)(i+j), offset, offset_width, &read_params, rx_data);
				res = hisport_read(fd, &read_params);
				if (res == 0)
				{
					is_found = true;
					break;
				}
			}

			if (!is_found) {
				printf("-- ");
			}
			else {
				printf("%02x ", i+j);
			}
		}
		printf("\n");
	}

	hisport_close(fd);
	return 0;
}

int main(int argc, char *argv[])
{
	char *end;
	int hisportbus, res;
	int mode = MODE_AUTO;
	int first = 0x03, last = 0x77;
	int flags = 0;
	int version = 0, list = 0;

	/* handle (optional) flags first */
	while (1+flags < argc && argv[1+flags][0] == '-') {
		switch (argv[1+flags][1]) {
		case 'V': version = 1; break;
		case 'l': list = 1; break;
		case 'r':
			if (mode == MODE_QUICK) {
				fprintf(stderr, "Error: Different modes "
					"specified!\n");
				exit(1);
			}
			mode = MODE_READ;
			break;
		case 'q':
			if (mode == MODE_READ) {
				fprintf(stderr, "Error: Different modes "
					"specified!\n");
				exit(1);
			}
			mode = MODE_QUICK;
			break;
		case 'a':
			first = 0x00;
			last = 0x7F;
			break;
		case 'h':
			help();
			exit(0);
		default:
			fprintf(stderr, "Error: Unsupported option "
				"\"%s\"!\n", argv[1+flags]);
			help();
			exit(1);
		}
		flags++;
	}

	if (version) {
		fprintf(stderr, "hisportdetect version %s\n", VERSION);
		exit(0);
	}

	if (list) {
		res = print_hisport_busses();
		exit(res?1:0);
	}

	if (argc < flags + 2) {
		fprintf(stderr, "Error: No hisport-bus specified!\n");
		help();
		exit(1);
	}
	
	/* Parse bus number using lookup function */
	hisportbus = lookup_hisport_bus(argv[flags+1]);
	if (hisportbus < 0) {
		help();
		exit(1);
	}

	/* read address range if present */
	if (argc == flags + 4 && mode != MODE_FUNC) {
		int tmp;

		tmp = strtol(argv[flags+2], &end, 0);
		if (*end) {
			fprintf(stderr, "Error: FIRST argument not a "
				"number!\n");
			exit(1);
		}
		if (tmp < first || tmp > last) {
			fprintf(stderr, "Error: FIRST argument out of range "
				"(0x%02x-0x%02x)!\n", first, last);
			exit(1);
		}
		first = tmp;

		tmp = strtol(argv[flags+3], &end, 0);
		if (*end) {
			fprintf(stderr, "Error: LAST argument not a "
				"number!\n");
			exit(1);
		}
		if (tmp < first || tmp > last) {
			fprintf(stderr, "Error: LAST argument out of range "
				"(0x%02x-0x%02x)!\n", first, last);
			exit(1);
		}
		last = tmp;
	} else if (argc != flags + 2) {
		help();
		exit(1);
	}

	/* Print command start message to ttyS0 */
	FILE *tty = fopen("/dev/ttyS0", "w");
	if (tty != NULL) {
		fprintf(tty, "hisportdetect command started - scanning bus %d, mode %d, range 0x%02x-0x%02x\n", 
			hisportbus, mode, first, last);
		fclose(tty);
	}

	res = scan_hisport_bus(hisportbus, mode, first, last);

	exit(res?1:0);
}
